import os
import platform
import re
import subprocess
import javalang
import threading
import xml.etree.ElementTree as ET
from LoggerManager import LoggerManager
from pathlib import Path
import shutil
from Config import JAVA_HOME, CKGConstruction_Jar_Path


# Warning: this is not a stable dependency, it may be removed in the future
# from TestAgent_wo_function_call.Config import CKGConstruction_Jar_Path
if platform.system() == "Windows":
    mvn_exe = "mvn.cmd"
else:
    mvn_exe = "mvn"

def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:  
            instances[cls] = cls(*args, **kwargs) 
        return instances[cls]

    return get_instance


def check_infor(func):
    def wrapper(self, *args, **kwargs):
        missing = []
        if self.inject_dir is None:
            missing.append("inject_dir")
        if self.test_file_name is None:
            missing.append("test_file_name")
        if self.test_code is None:
            missing.append("test_code")
        if self.base_path is None:
            missing.append("base_path")
        if missing:
            pass # evosuite special
            # raise Exception(f"The following variables must be set before running test: {', '.join(missing)}.")
        return func(self, *args, **kwargs)

    return wrapper


# @singleton
class EnvironmentService:
    def __init__(self):
        """
        Initialize the environment service class for managing test environment operations.
        The parameter settings are divided into three parts: base_path is the command execution directory, 
        which is the same for the same project, so it should be set during initialization;
        inject_dir is the directory for injecting test code, and the injection location is different each time, 
        so it should be set before testing the same method under test;
        test_file_name and test_code are the file name and code content of the test code, 
        so they should be set before each test.
        """
        self._current_path = os.getcwd()
        self.base_path = None
        self.inject_dir = None
        self.test_file_name = None
        self.test_code = None
        self.ready_to_test = False
        self.lock = threading.Lock()  # create lock
        self.number = 1
        self.env = os.environ.copy()
        self.env["JAVA_HOME"] = JAVA_HOME
        self.env["PATH"] = self.env["JAVA_HOME"] + "/bin:" + self.env["PATH"]

    @property
    def current_path(self):
        return self._current_path

    def set_base_path(self, base_path):
        self.base_path = base_path

    def set_inject_dir(self, inject_dir):
        self.inject_dir = inject_dir

    def get_inject_dir(self):
        return self.inject_dir

    def set_test_infor(self, test_file_name, test_code):
        self.test_file_name = test_file_name
        self.test_code = test_code
        if self.test_file_name.endswith(".java"):
            self.test_file_name = self.test_file_name[:-5]

    def set_test_file_name(self, test_file_name):
        self.test_file_name = test_file_name
        self.ready_to_test = True

    @check_infor
    def syntax_check(self):
        try:
            javalang.parse.parse(self.test_code)
            return True, "Syntax check passed."
        except javalang.parser.JavaSyntaxError as e:
            return False, e

    @check_infor
    def test_code_inject(self):
        inject_path = os.path.join(self.inject_dir, self.test_file_name + ".java")
        if not os.path.exists(self.inject_dir):
            os.makedirs(self.inject_dir)
        try:
            with open(inject_path, "w") as f:
                f.write(self.test_code)
                self.ready_to_test = True
        except Exception:
            raise Exception(f"Failed to inject test code to file: {inject_path}.")

    def total_test_remove(self):
        """
        Delete all test cases under src/test directory.
        """
        if not self.base_path:
            raise Exception("base_path is not set. Please set it before removing tests.")

        target_dir = os.path.join(self.base_path, "src", "test")
        if os.path.exists(target_dir):
            try:
                subprocess.run("rm -rf src/test/*", shell=True, check=True, cwd=self.base_path)
                print("[✓] All test cases have been removed from src/test.")
            except subprocess.CalledProcessError as e:
                raise RuntimeError(f"Fail to remove test cases: {e}")
        else:
            print("[!] src/test directory does not exist.")


    @check_infor
    def test_code_remove(self):
        inject_path = os.path.join(self.inject_dir, self.test_file_name + ".java")
        self.ready_to_test = False
        if os.path.exists(inject_path):
            os.remove(inject_path)
            return True
        return False

    @check_infor
    def compile_test(self):
        if not self.ready_to_test:
            raise Exception("Environment is not ready to run test.")

        LoggerManager().logger.info(f"=============== START COMPILE TEST ===============")
        # print("=============== START COMPILE TEST ===============")
        compile_command = [mvn_exe, "clean", "test-compile", f"-Dtest={self.test_file_name}"]
        LoggerManager().logger.info(f"execute command: {' '.join(compile_command)}")
        print(f"execute command: {' '.join(compile_command)}")

        result = subprocess.run(compile_command, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, text=True, cwd=self.base_path)

        compile_output = result.stdout
        if "BUILD SUCCESS" in compile_output:
            return True, compile_output
        elif "BUILD FAILURE" in compile_output:
            return False, compile_output
        else:
            raise Exception("can not find result in compile output.")

    @check_infor
    def execute_test(self):
        if not self.ready_to_test:
            raise Exception("Environment is not ready to run test.")

        LoggerManager().logger.info(f"=============== START EXECUTE TEST ===============")
        # print("=============== START EXECUTE TEST ===============")
        test_command = [mvn_exe, "clean", "test", f"-Dtest={self.test_file_name}"]
        LoggerManager().logger.info(f"execute command: {' '.join(test_command)}")
        # print(f"execute command: {' '.join(test_command)}")

        try:
            # Set timeout to 30 seconds
            result = subprocess.run(test_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, timeout=30, env=self.env)

            test_output = result.stdout
            if "BUILD SUCCESS" in test_output:
                return True, test_output
            elif "BUILD FAILURE" in test_output:
                return False, test_output
            else:
                raise Exception("can not find result in execute output.")

        except subprocess.TimeoutExpired as e:
            # Timeout handling
            return False, f"Time out: The process exceeded the 20-second limit. Command: {' '.join(test_command)}"

        except Exception as e:
            # Other exception handling
            return False, f"Error: {str(e)}"

    @check_infor
    def total_execute_test(self):
        if not self.ready_to_test:
            raise Exception("Environment is not ready to run test.")

        LoggerManager().logger.info(f"=============== START EXECUTE TEST ===============")
        # print("=============== START EXECUTE TEST ===============")
        test_command = [mvn_exe, "clean", "test"]
        LoggerManager().logger.info(f"execute command: {' '.join(test_command)}")
        # print(f"execute command: {' '.join(test_command)}")

        try:
            # Set timeout to 30 seconds
            result = subprocess.run(test_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path)

            test_output = result.stdout
            if "BUILD SUCCESS" in test_output:
                return True, test_output
            elif "BUILD FAILURE" in test_output:
                return False, test_output
            else:
                raise Exception("can not find result in execute output.")

        except subprocess.TimeoutExpired as e:
            # Timeout handling
            return False, f"Time out: The process exceeded the 20-second limit. Command: {' '.join(test_command)}"

        except Exception as e:
            # Other exception handling
            return False, f"Error: {str(e)}"

    @check_infor
    def dependency_generate(self):
        LoggerManager().logger.info(f"=============== START DEPENDENCY GENERATE ===============")
        # print("=============== START DEPENDENCY GENERATE ===============")
        dependency_command = [mvn_exe, "dependency:copy-dependencies"]
        LoggerManager().logger.info(f"execute command: {' '.join(dependency_command)}")
        # print(f"execute command: {' '.join(dependency_command)}")

        result = subprocess.run(dependency_command, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, text=True, cwd=self.base_path)
        test_output = result.stdout
        if "BUILD SUCCESS" in test_output:
            return True, test_output
        elif "BUILD FAILURE" in test_output:
            return False, test_output
        else:
            raise Exception("can not find result in execute output.")

    def run_compile_test(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            syntax_result, syntax_output = self.syntax_check()
            if not syntax_result:
                return {"result": "Syntax Error", "output": syntax_output}

            self.test_code_inject()
            compile_result, compile_output = self.compile_test()
            if not compile_result:
                self.test_code_remove()
                return {"result": "Compile Error", "output": compile_output}
            self.test_code_remove()
            return {"result": "Success", "output": compile_output}

    def run_execute_test(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            execute_result, execute_output = self.execute_test()
            if not execute_result:
                self.test_code_remove()
                return {"result": "Execute Error", "output": execute_output}
            self.test_code_remove()
            return {"result": "Success", "output": execute_output}

    def run_compile_and_execute_test(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            syntax_result, syntax_output = self.syntax_check()
            if not syntax_result:
                return {"result": "Syntax Error", "output": syntax_output}

            self.test_code_inject()
            compile_result, compile_output = self.compile_test()
            if not compile_result:
                self.test_code_remove()
                return {"result": "Compile Error", "output": compile_output}
            execute_result, execute_output = self.execute_test()
            if not execute_result:
                self.test_code_remove()
                return {"result": "Execute Error", "output": execute_output}
            self.test_code_remove()
            return {"result": "Success", "output": execute_output}

    def direct_run_compile_execute_test(self):
        self.ready_to_test = True

        execute_result, execute_output = self.total_execute_test()
        if not execute_result:
            return {"result": "Execute Error", "output": execute_output}
        return {"result": "Success", "output": execute_output}

    def simple_run_coverage_test(self, package_name, class_name, focal_method_signature: str, start_line: int, end_line: int):
        with self.lock:
            self.total_execute_test()
            exec_file = os.path.join(self.base_path, "target", "jacoco.exec")
            if not os.path.exists(exec_file):
                # self.test_code_remove()
                return {"result": "Error", "output": "Jacoco exec file not found."}

            LoggerManager().logger.info(f"=============== START COVERAGE CALCULATE ===============")
            # print("=============== START COVERAGE CALCULATE ===============")
            coverage_command = [mvn_exe, "jacoco:report"]
            LoggerManager().logger.info(f"execute command: {' '.join(coverage_command)}")
            # print(f"execute command: {' '.join(coverage_command)}")

            result = subprocess.run(coverage_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path)

            coverage_output = result.stdout
            # self.test_code_remove()
            if "BUILD SUCCESS" in coverage_output:
                return self.parse_jacoco_report(package_name, class_name, focal_method_signature, start_line, end_line)
            elif "BUILD FAILURE" in coverage_output:
                return {"result": "Error", "output": coverage_output}
            else:
                raise Exception("can not find result in compile output.")

    def direct_run_coverage_test(self, test_file_name: str, package_name, class_name,
                          focal_method_signature: str, boundary_list: list):
        with self.lock:
            self.set_test_file_name(test_file_name)
            self.execute_test()
            exec_file = os.path.join(self.base_path, "target", "jacoco.exec")
            if not os.path.exists(exec_file):
                # self.test_code_remove()
                return [{"result": "Error", "output": "Jacoco exec file not found."}]

            LoggerManager().logger.info(f"=============== START COVERAGE CALCULATE ===============")
            # print("=============== START COVERAGE CALCULATE ===============")
            coverage_command = [mvn_exe, "jacoco:report"]
            LoggerManager().logger.info(f"execute command: {' '.join(coverage_command)}")
            # print(f"execute command: {' '.join(coverage_command)}")

            result = subprocess.run(coverage_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            coverage_output = result.stdout
            # self.test_code_remove()
            if "BUILD SUCCESS" in coverage_output:
                return [self.parse_jacoco_report(package_name, class_name, "", start_line, end_line) for start_line, end_line in boundary_list]
            elif "BUILD FAILURE" in coverage_output:
                return [{"result": "Error", "output": coverage_output}]
            else:
                raise Exception("can not find result in compile output.")

    def run_coverage_test(self, test_code: str, test_file_name: str, package_name, class_name,
                          focal_method_signature: str, start_line: int, end_line: int):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            self.execute_test()
            exec_file = os.path.join(self.base_path, "target", "jacoco.exec")
            if not os.path.exists(exec_file):
                self.test_code_remove()
                return {"result": "Error", "output": "Jacoco exec file not found."}

            LoggerManager().logger.info(f"=============== START COVERAGE CALCULATE ===============")
            # print("=============== START COVERAGE CALCULATE ===============")
            coverage_command = [mvn_exe, "jacoco:report"]
            LoggerManager().logger.info(f"execute command: {' '.join(coverage_command)}")
            # print(f"execute command: {' '.join(coverage_command)}")

            result = subprocess.run(coverage_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            coverage_output = result.stdout
            self.test_code_remove()
            if "BUILD SUCCESS" in coverage_output:
                return self.parse_jacoco_report(package_name, class_name, focal_method_signature, start_line, end_line)
            elif "BUILD FAILURE" in coverage_output:
                return {"result": "Error", "output": coverage_output}
            else:
                raise Exception("can not find result in compile output.")

    def parse_jacoco_report(self, package_name, class_name, focal_method_signature: str, start_line: int,
                            end_line: int):
        """
        Parser JaCoCo XML report, get line coverage and branch coverage of the target method, and extract specific covered line data from detailed coverage information.

        :param package_name: Java package name, "org.apache.commons.cli"
        :param class_name: Java class name, "org.apache.commons.cli.OptionValidator"
        :param focal_method_signature: target method name, "validate"
        :param start_line: start line number of the target method, 60
        :param end_line: end line number of the target method, 83
        :return: dictionary { "line_coverage": float, "branch_coverage": float, "covered_lines": list, "missed_lines": list }
        """
        xml_path = os.path.join(self.base_path, "target", "site", "jacoco", "jacoco.xml")
        if not os.path.exists(xml_path):
            return {"result": "Error", "output": "Jacoco XML report not found."}
        tree = ET.parse(xml_path)
        root = tree.getroot()
        package_name = package_name.replace(".", "/")
        class_name = class_name.replace(".", "/")
        # First, find the sourcefilename corresponding to the class
        source_file_name = None
        # Then, find the target method in the class and extract the coverage information of each line
        covered_lines = set()
        missed_lines = set()
        total_lines, covered_lines_count = 0, 0
        total_branches, covered_branches_count = 0, 0

        for package in root.findall("package"):
            if package.get("name") != package_name:
                continue  # Only look for the target package

            for class_element in package.findall("class"):
                if class_element.get("name") == class_name or class_element.get("name").endswith("/" + class_name):
                    source_file_name = class_element.get("sourcefilename")
                    break

            if not source_file_name:
                return {"result": "Error", "output": "Source file not found."}

            for source_file in package.findall("sourcefile"):
                if source_file.get("name") != source_file_name:
                    continue  # Only look for the target sourcefile

                for line in source_file.findall("line"):
                    line_number = int(line.get("nr"))
                    if not (start_line <= line_number <= end_line):
                        continue  # Filter out lines that are not within the method range

                    missed_instr = int(line.get("mi", "0"))  # missed instructions
                    covered_instr = int(line.get("ci", "0"))  # covered instructions
                    missed_branches = int(line.get("mb", "0"))  # missed branches
                    covered_branches = int(line.get("cb", "0"))  # covered branches

                    # Record covered and missed lines
                    if missed_instr == 0 and covered_instr > 0:
                        covered_lines.add(line_number)
                    else:
                        missed_lines.add(line_number)

                    # Calculate line coverage
                    covered_lines_count += covered_instr > 0
                    total_lines += 1

                    # Calculate branch coverage
                    covered_branches_count += covered_branches
                    total_branches += missed_branches + covered_branches

        # Calculate final coverage
        line_coverage = (covered_lines_count / total_lines) * 100 if total_lines > 0 else 0
        branch_coverage = (covered_branches_count / total_branches) * 100 if total_branches > 0 else line_coverage  # 无BRANCH时用LINE替代

        return {
            "result": "Success",
            "output": {
                "function_name": focal_method_signature,
                "line_coverage": line_coverage,
                "branch_coverage": branch_coverage,
                "covered_lines": sorted(list(covered_lines)),
                "missed_lines": sorted(list(missed_lines))
            }
        }

    def simple_run_mutation_test(self, method_name, class_name, start_line, end_line):
        with self.lock:
            self.total_execute_test()
            LoggerManager().logger.info(f"=============== START MUTATION CALCULATE ===============")
            mutation_command = [mvn_exe, "org.pitest:pitest-maven:mutationCoverage",]
                                # f"-DtargetClasses={class_name}"]
            LoggerManager().logger.info(f"execute command: {' '.join(mutation_command)}")

            result = subprocess.run(mutation_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path)

            mutation_output = result.stdout
            # self.test_code_remove()
            if "BUILD SUCCESS" in mutation_output:
                return self.exact_parse_pitest_report(start_line, end_line, class_name, method_name)
            elif "BUILD FAILURE" in mutation_output:
                return {"result": "Error", "output": mutation_output}
            else:
                raise Exception("can not find result in mutation output.")

    def simple_run_mutation_test_1(self, method_name, class_name, start_line, end_line):
        with self.lock:
            self.total_execute_test()
            LoggerManager().logger.info(f"=============== START MUTATION CALCULATE ===============")
            mutation_command = [mvn_exe, "org.pitest:pitest-maven:mutationCoverage",
                                f"-DtargetClasses={class_name}"]
            LoggerManager().logger.info(f"execute command: {' '.join(mutation_command)}")

            result = subprocess.run(mutation_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path)

            mutation_output = result.stdout
            self.test_code_remove()
            if "BUILD SUCCESS" in mutation_output:
                return self.parse_pitest_report(start_line, end_line)
            elif "BUILD FAILURE" in mutation_output:
                return {"result": "Error", "output": mutation_output}
            else:
                raise Exception("can not find result in mutation output.")

    def direct_run_mutation_test(self, package_name: str, focal_class_name: str, test_file_name: str, boundary_list: list):
        # "mvn org.pitest:pitest-maven:mutationCoverage -DtargetClasses=org.apache.commons.cli.Option -DtargetTests=org.apache.commons.cli.OptionTest"
        with self.lock:
            self.set_test_file_name(test_file_name)
            execute_result, execute_output = self.execute_test()
            if not execute_result:
                # self.test_code_remove()
                return [{"result": "Error", "output": "Compilation or execution error. Environment is not ready to run mutation test."}]
            class_name = package_name + "." + focal_class_name
            test_name = package_name + "." + self.test_file_name
            LoggerManager().logger.info(f"=============== START MUTATION TEST ===============")
            # print("=============== START MUTATION TEST ===============")
            mutation_command = [mvn_exe, "org.pitest:pitest-maven:mutationCoverage",
                                f"-DtargetClasses={class_name}",
                                f"-DtargetTests={test_name}"]
            LoggerManager().logger.info(f"execute command: {' '.join(mutation_command)}")
            # print(f"execute command: {' '.join(mutation_command)}")

            result = subprocess.run(mutation_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            mutation_output = result.stdout
            # self.test_code_remove()
            if "BUILD SUCCESS" in mutation_output:
                return [self.parse_pitest_report(start_line, end_line) for start_line, end_line in boundary_list]
            elif "BUILD FAILURE" in mutation_output:
                return [{"result": "Error", "output": mutation_output}]
            else:
                raise Exception("can not find result in mutation output.")

    def run_mutation_test(self, package_name: str, focal_class_name: str, test_code: str, test_file_name: str, start_line: int, end_line: int):
        # "mvn org.pitest:pitest-maven:mutationCoverage -DtargetClasses=org.apache.commons.cli.Option -DtargetTests=org.apache.commons.cli.OptionTest"
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            execute_result, execute_output = self.execute_test()
            if not execute_result:
                self.test_code_remove()
                return {"result": "Error", "output": "Compilation or execution error. Environment is not ready to run mutation test."}
            class_name = package_name + "." + focal_class_name
            test_name = package_name + "." + self.test_file_name
            LoggerManager().logger.info(f"=============== START MUTATION TEST ===============")
            # print("=============== START MUTATION TEST ===============")
            mutation_command = [mvn_exe, "org.pitest:pitest-maven:mutationCoverage",
                                f"-DtargetClasses={class_name}",
                                f"-DtargetTests={test_name}"]
            LoggerManager().logger.info(f"execute command: {' '.join(mutation_command)}")
            # print(f"execute command: {' '.join(mutation_command)}")

            result = subprocess.run(mutation_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            mutation_output = result.stdout
            self.test_code_remove()
            if "BUILD SUCCESS" in mutation_output:
                return self.parse_pitest_report(start_line, end_line)
            elif "BUILD FAILURE" in mutation_output:
                return {"result": "Error", "output": mutation_output}
            else:
                raise Exception("can not find result in mutation output.")

    def parse_pitest_report(self, start_line: int, end_line: int):
        """
        Parser Pitest XML report, get mutation score and extract specific mutation information from detailed report.

        :param start_line: start line number of the target method, 60
        :param end_line: end line number of the target method, 83
        :return: dictionary { "mutation_score": float, "filtered_mutations": list }

        """
        xml_path = os.path.join(self.base_path, "target", "pit-reports", "mutations.xml")
        if not os.path.exists(xml_path):
            return {"result": "Error", "output": "Jacoco XML report not found."}
        tree = ET.parse(xml_path)
        root = tree.getroot()

        total_mutations = 0
        survived_mutants = 0
        filtered_mutations = []

        for mutation in root.findall(".//mutation"):
            # extract mutation information
            line_number = int(mutation.find("lineNumber").text)
            if not (start_line <= line_number <= end_line):
                # Only keep mutations within the specified line range
                continue  
            
            total_mutations += 1
            status = mutation.get("status")
            class_name = mutation.find("mutatedClass").text
            method_name = mutation.find("mutatedMethod").text
            description = mutation.find("description").text
            mutator = mutation.find("mutator").text
            num_tests = mutation.get("numberOfTestsRun")
            killing_test = mutation.find("killingTest").text if mutation.find("killingTest") is not None else "None"

            # Statistic the survived mutants
            if status != "KILLED":
                survived_mutants += 1

            # Store the detailed information of the mutation
            filtered_mutations.append({
                "Class": class_name,
                "Method": method_name,
                "Line": line_number,
                "Mutator": mutator,
                "Description": description,
                "Status": status,
                "Tests Run": num_tests,
                "Killing Test": killing_test
            })

        # Calculate the mutation score
        killed_mutants = total_mutations - survived_mutants
        mutation_score = (killed_mutants / total_mutations) * 100 if total_mutations > 0 else 0

        return {
            "result": "Success",
            "output": {
                "mutation_score": mutation_score,
                "filtered_mutations": filtered_mutations
            }
        }

    def exact_parse_pitest_report(self, start_line: int, end_line: int, original_class_name, original_method_name):
        """
        Parser Pitest XML report, get mutation score and extract specific mutation information from detailed report.

        :param start_line: start line number of the target method, 60
        :param end_line: end line number of the target method, 83
        :return: dictionary { "mutation_score": float, "filtered_mutations": list }
        """
        xml_path = os.path.join(self.base_path, "target", "pit-reports", "mutations.xml")
        if not os.path.exists(xml_path):
            return {"result": "Error", "output": "Jacoco XML report not found."}
        tree = ET.parse(xml_path)
        root = tree.getroot()

        total_mutations = 0
        survived_mutants = 0
        filtered_mutations = []

        for mutation in root.findall(".//mutation"):
            # extract mutation information
            line_number = int(mutation.find("lineNumber").text)
            if not (start_line <= line_number <= end_line):
                # Only keep mutations within the specified line range
                continue  

            status = mutation.get("status")
            class_name = mutation.find("mutatedClass").text
            method_name = mutation.find("mutatedMethod").text
            description = mutation.find("description").text
            mutator = mutation.find("mutator").text
            num_tests = mutation.get("numberOfTestsRun")
            killing_test = mutation.find("killingTest").text if mutation.find("killingTest") is not None else "None"

            if (not class_name.endswith(original_class_name)) or method_name != original_method_name:
                continue
            total_mutations += 1

            # Statistic the survived mutants
            if status != "KILLED":
                survived_mutants += 1

            # Store the detailed information of the mutation
            filtered_mutations.append({
                "Class": class_name,
                "Method": method_name,
                "Line": line_number,
                "Mutator": mutator,
                "Description": description,
                "Status": status,
                "Tests Run": num_tests,
                "Killing Test": killing_test
            })

        # Calculate the mutation score
        killed_mutants = total_mutations - survived_mutants
        mutation_score = (killed_mutants / total_mutations) * 100 if total_mutations > 0 else 0

        return {
            "result": "Success",
            "output": {
                "mutation_score": mutation_score,
                "filtered_mutations": filtered_mutations
            }
        }

    # def simple_fix(self, test_code):
    #     if "package" in test_code:
    #         test_code = test_code.replace("package", "// package")
    #     marker = "src/test/java" if self.inject_dir.find("src/test/java") != -1 else "src/test"
    #     package_name = self.inject_dir.split(marker, 1)[-1].strip(os.sep).replace(os.sep, ".")
    #     test_code = "package " + package_name + ";\n\n" + test_code
    #     return test_code

    def simple_fix(self, test_code):
        if "package" in test_code:
            test_code = test_code.replace("package", "// package")

        marker = os.path.join("src", "test", "java")
        try:
            rel_path = self.inject_dir.split(marker, 1)[1]  # 只取 marker 之后的部分
        except IndexError:
            rel_path = ""  # 没有匹配到 marker，留空或 fallback

        # 去掉前后的路径分隔符，替换成 .
        package_name = rel_path.strip(os.sep).replace(os.sep, ".")
        if not package_name:
            package_name = "defaultpkg"  # 防止为空时编译错误

        test_code = f"package {package_name};\n\n" + test_code
        return test_code

    def add_test_to_CKG(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            self.compile_test()
            self.dependency_generate()
            jar_path = CKGConstruction_Jar_Path
            cmd = ["java", "-jar", jar_path,
                   "-p", self.inject_dir,
                   "-u", "bolt://localhost:7687",
                   "-n", "neo4j",
                   "-w", "123456",
                   "-t",
                   "-c"]
            result = subprocess.run(cmd, capture_output=True, text=True)
            self.test_code_remove()
            if result.returncode == 0:
                return {"result": "Success", "output": result.stdout}
            else:
                return {"result": "Error", "output": result.stderr}

